// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/cdm/json_web_key.h"

#include <stddef.h>
#include <stdint.h>

#include "base/base64.h"
#include "base/logging.h"
#include "base/macros.h"
#include "media/base/content_decryption_module.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace media {

class JSONWebKeyTest : public testing::Test {
public:
    JSONWebKeyTest() { }

protected:
    void ExtractJWKKeysAndExpect(const std::string& jwk,
        bool expected_result,
        size_t expected_number_of_keys)
    {
        DCHECK(!jwk.empty());
        KeyIdAndKeyPairs keys;
        CdmSessionType session_type;
        EXPECT_EQ(expected_result,
            ExtractKeysFromJWKSet(jwk, &keys, &session_type));
        EXPECT_EQ(expected_number_of_keys, keys.size());
    }

    void ExtractSessionTypeAndExpect(const std::string& jwk,
        bool expected_result,
        CdmSessionType expected_type)
    {
        DCHECK(!jwk.empty());
        KeyIdAndKeyPairs keys;
        CdmSessionType session_type;
        EXPECT_EQ(expected_result,
            ExtractKeysFromJWKSet(jwk, &keys, &session_type));
        if (expected_result) {
            // Only check if successful.
            EXPECT_EQ(expected_type, session_type);
        }
    }

    void CreateLicenseAndExpect(const uint8_t* key_id,
        int key_id_length,
        CdmSessionType session_type,
        const std::string& expected_result)
    {
        std::vector<uint8_t> result;
        KeyIdList key_ids;
        key_ids.push_back(std::vector<uint8_t>(key_id, key_id + key_id_length));
        CreateLicenseRequest(key_ids, session_type, &result);
        std::string s(result.begin(), result.end());
        EXPECT_EQ(expected_result, s);
    }

    void ExtractKeyFromLicenseAndExpect(const std::string& license,
        bool expected_result,
        const uint8_t* expected_key,
        int expected_key_length)
    {
        std::vector<uint8_t> license_vector(license.begin(), license.end());
        std::vector<uint8_t> key;
        EXPECT_EQ(expected_result,
            ExtractFirstKeyIdFromLicenseRequest(license_vector, &key));
        if (expected_result)
            VerifyKeyId(key, expected_key, expected_key_length);
    }

    void VerifyKeyId(std::vector<uint8_t> key,
        const uint8_t* expected_key,
        int expected_key_length)
    {
        std::vector<uint8_t> key_result(expected_key,
            expected_key + expected_key_length);
        EXPECT_EQ(key_result, key);
    }

    KeyIdAndKeyPair MakeKeyIdAndKeyPair(const uint8_t* key,
        int key_length,
        const uint8_t* key_id,
        int key_id_length)
    {
        return std::make_pair(std::string(key_id, key_id + key_id_length),
            std::string(key, key + key_length));
    }
};

TEST_F(JSONWebKeyTest, GenerateJWKSet)
{
    const uint8_t data1[] = { 0x01, 0x02 };
    const uint8_t data2[] = { 0x01, 0x02, 0x03, 0x04 };
    const uint8_t data3[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
        0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 };

    EXPECT_EQ("{\"keys\":[{\"k\":\"AQI\",\"kid\":\"AQI\",\"kty\":\"oct\"}]}",
        GenerateJWKSet(data1, arraysize(data1), data1, arraysize(data1)));
    EXPECT_EQ(
        "{\"keys\":[{\"k\":\"AQIDBA\",\"kid\":\"AQIDBA\",\"kty\":\"oct\"}]}",
        GenerateJWKSet(data2, arraysize(data2), data2, arraysize(data2)));
    EXPECT_EQ("{\"keys\":[{\"k\":\"AQI\",\"kid\":\"AQIDBA\",\"kty\":\"oct\"}]}",
        GenerateJWKSet(data1, arraysize(data1), data2, arraysize(data2)));
    EXPECT_EQ("{\"keys\":[{\"k\":\"AQIDBA\",\"kid\":\"AQI\",\"kty\":\"oct\"}]}",
        GenerateJWKSet(data2, arraysize(data2), data1, arraysize(data1)));
    EXPECT_EQ(
        "{\"keys\":[{\"k\":\"AQIDBAUGBwgJCgsMDQ4PEA\",\"kid\":"
        "\"AQIDBAUGBwgJCgsMDQ4PEA\",\"kty\":\"oct\"}]}",
        GenerateJWKSet(data3, arraysize(data3), data3, arraysize(data3)));

    KeyIdAndKeyPairs keys;
    keys.push_back(
        MakeKeyIdAndKeyPair(data1, arraysize(data1), data1, arraysize(data1)));
    EXPECT_EQ(
        "{\"keys\":[{\"k\":\"AQI\",\"kid\":\"AQI\",\"kty\":\"oct\"}],\"type\":"
        "\"temporary\"}",
        GenerateJWKSet(keys, CdmSessionType::TEMPORARY_SESSION));
    keys.push_back(
        MakeKeyIdAndKeyPair(data2, arraysize(data2), data2, arraysize(data2)));
    EXPECT_EQ(
        "{\"keys\":[{\"k\":\"AQI\",\"kid\":\"AQI\",\"kty\":\"oct\"},{\"k\":"
        "\"AQIDBA\",\"kid\":\"AQIDBA\",\"kty\":\"oct\"}],\"type\":\"persistent-"
        "license\"}",
        GenerateJWKSet(keys, CdmSessionType::PERSISTENT_LICENSE_SESSION));
    keys.push_back(
        MakeKeyIdAndKeyPair(data3, arraysize(data3), data3, arraysize(data3)));
    EXPECT_EQ(
        "{\"keys\":[{\"k\":\"AQI\",\"kid\":\"AQI\",\"kty\":\"oct\"},{\"k\":"
        "\"AQIDBA\",\"kid\":\"AQIDBA\",\"kty\":\"oct\"},{\"k\":"
        "\"AQIDBAUGBwgJCgsMDQ4PEA\",\"kid\":\"AQIDBAUGBwgJCgsMDQ4PEA\",\"kty\":"
        "\"oct\"}],\"type\":\"persistent-release-message\"}",
        GenerateJWKSet(keys, CdmSessionType::PERSISTENT_RELEASE_MESSAGE_SESSION));
}

TEST_F(JSONWebKeyTest, ExtractValidJWKKeys)
{
    // Try an empty 'keys' dictionary.
    ExtractJWKKeysAndExpect("{ \"keys\": [] }", true, 0);

    // Try a key list with one entry.
    const std::string kJwksOneEntry = "{"
                                      "  \"keys\": ["
                                      "    {"
                                      "      \"kty\": \"oct\","
                                      "      \"kid\": \"AAECAwQFBgcICQoLDA0ODxAREhM\","
                                      "      \"k\": \"FBUWFxgZGhscHR4fICEiIw\""
                                      "    }"
                                      "  ]"
                                      "}";
    ExtractJWKKeysAndExpect(kJwksOneEntry, true, 1);

    // Try a key list with multiple entries.
    const std::string kJwksMultipleEntries = "{"
                                             "  \"keys\": ["
                                             "    {"
                                             "      \"kty\": \"oct\","
                                             "      \"kid\": \"AAECAwQFBgcICQoLDA0ODxAREhM\","
                                             "      \"k\": \"FBUWFxgZGhscHR4fICEiIw\""
                                             "    },"
                                             "    {"
                                             "      \"kty\": \"oct\","
                                             "      \"kid\": \"JCUmJygpKissLS4vMA\","
                                             "      \"k\":\"MTIzNDU2Nzg5Ojs8PT4_QA\""
                                             "    }"
                                             "  ]"
                                             "}";
    ExtractJWKKeysAndExpect(kJwksMultipleEntries, true, 2);

    // Try a key with no spaces and some \n plus additional fields.
    const std::string kJwksNoSpaces = "\n\n{\"something\":1,\"keys\":[{\n\n\"kty\":\"oct\","
                                      "\"kid\":\"AAECAwQFBgcICQoLDA0ODxAREhM\",\"k\":\"GawgguFyGrWKav7AX4VKUg"
                                      "\",\"foo\":\"bar\"}]}\n\n";
    ExtractJWKKeysAndExpect(kJwksNoSpaces, true, 1);

    // Try a list with multiple keys with the same kid.
    const std::string kJwksDuplicateKids = "{"
                                           "  \"keys\": ["
                                           "    {"
                                           "      \"kty\": \"oct\","
                                           "      \"kid\": \"JCUmJygpKissLS4vMA\","
                                           "      \"k\": \"FBUWFxgZGhscHR4fICEiIw\""
                                           "    },"
                                           "    {"
                                           "      \"kty\": \"oct\","
                                           "      \"kid\": \"JCUmJygpKissLS4vMA\","
                                           "      \"k\":\"MTIzNDU2Nzg5Ojs8PT4_QA\""
                                           "    }"
                                           "  ]"
                                           "}";
    ExtractJWKKeysAndExpect(kJwksDuplicateKids, true, 2);
}

TEST_F(JSONWebKeyTest, ExtractInvalidJWKKeys)
{
    // Try a simple JWK key (i.e. not in a set)
    const std::string kJwkSimple = "{"
                                   "  \"kty\": \"oct\","
                                   "  \"kid\": \"AAECAwQFBgcICQoLDA0ODxAREhM\","
                                   "  \"k\": \"FBUWFxgZGhscHR4fICEiIw\""
                                   "}";
    ExtractJWKKeysAndExpect(kJwkSimple, false, 0);

    // Try some non-ASCII characters.
    ExtractJWKKeysAndExpect(
        "This is not ASCII due to \xff\xfe\xfd in it.", false, 0);

    // Try some non-ASCII characters in an otherwise valid JWK.
    const std::string kJwksInvalidCharacters = "\n\n{\"something\":1,\"keys\":[{\n\n\"kty\":\"oct\","
                                               "\"kid\":\"AAECAwQFBgcICQoLDA0ODxAREhM\",\"k\":\"\xff\xfe\xfd"
                                               "\",\"foo\":\"bar\"}]}\n\n";
    ExtractJWKKeysAndExpect(kJwksInvalidCharacters, false, 0);

    // Try a badly formatted key. Assume that the JSON parser is fully tested,
    // so we won't try a lot of combinations. However, need a test to ensure
    // that the code doesn't crash if invalid JSON received.
    ExtractJWKKeysAndExpect("This is not a JSON key.", false, 0);

    // Try passing some valid JSON that is not a dictionary at the top level.
    ExtractJWKKeysAndExpect("40", false, 0);

    // Try an empty dictionary.
    ExtractJWKKeysAndExpect("{ }", false, 0);

    // Try with 'keys' not a dictionary.
    ExtractJWKKeysAndExpect("{ \"keys\":\"1\" }", false, 0);

    // Try with 'keys' a list of integers.
    ExtractJWKKeysAndExpect("{ \"keys\": [ 1, 2, 3 ] }", false, 0);

    // Try padding(=) at end of 'k' base64 string.
    const std::string kJwksWithPaddedKey = "{"
                                           "  \"keys\": ["
                                           "    {"
                                           "      \"kty\": \"oct\","
                                           "      \"kid\": \"AAECAw\","
                                           "      \"k\": \"BAUGBwgJCgsMDQ4PEBESEw==\""
                                           "    }"
                                           "  ]"
                                           "}";
    ExtractJWKKeysAndExpect(kJwksWithPaddedKey, false, 0);

    // Try padding(=) at end of 'kid' base64 string.
    const std::string kJwksWithPaddedKeyId = "{"
                                             "  \"keys\": ["
                                             "    {"
                                             "      \"kty\": \"oct\","
                                             "      \"kid\": \"AAECAw==\","
                                             "      \"k\": \"BAUGBwgJCgsMDQ4PEBESEw\""
                                             "    }"
                                             "  ]"
                                             "}";
    ExtractJWKKeysAndExpect(kJwksWithPaddedKeyId, false, 0);

    // Try a key with invalid base64 encoding.
    const std::string kJwksWithInvalidBase64 = "{"
                                               "  \"keys\": ["
                                               "    {"
                                               "      \"kty\": \"oct\","
                                               "      \"kid\": \"!@#$%^&*()\","
                                               "      \"k\": \"BAUGBwgJCgsMDQ4PEBESEw\""
                                               "    }"
                                               "  ]"
                                               "}";
    ExtractJWKKeysAndExpect(kJwksWithInvalidBase64, false, 0);

    // Empty key id.
    const std::string kJwksWithEmptyKeyId = "{"
                                            "  \"keys\": ["
                                            "    {"
                                            "      \"kty\": \"oct\","
                                            "      \"kid\": \"\","
                                            "      \"k\": \"BAUGBwgJCgsMDQ4PEBESEw\""
                                            "    }"
                                            "  ]"
                                            "}";
    ExtractJWKKeysAndExpect(kJwksWithEmptyKeyId, false, 0);
}

TEST_F(JSONWebKeyTest, KeyType)
{
    // Valid key type.
    const std::string kJwksWithValidKeyType = "{"
                                              "  \"keys\": ["
                                              "    {"
                                              "      \"kty\": \"oct\","
                                              "      \"kid\": \"AAECAwQFBgcICQoLDA0ODxAREhM\","
                                              "      \"k\": \"BAUGBwgJCgsMDQ4PEBESEw\""
                                              "    }"
                                              "  ]"
                                              "}";
    ExtractJWKKeysAndExpect(kJwksWithValidKeyType, true, 1);

    // Empty key type.
    const std::string kJwksWithEmptyKeyType = "{"
                                              "  \"keys\": ["
                                              "    {"
                                              "      \"kty\": \"\","
                                              "      \"kid\": \"AAECAwQFBgcICQoLDA0ODxAREhM\","
                                              "      \"k\": \"BAUGBwgJCgsMDQ4PEBESEw\""
                                              "    }"
                                              "  ]"
                                              "}";
    ExtractJWKKeysAndExpect(kJwksWithEmptyKeyType, false, 0);

    // Key type is case sensitive.
    const std::string kJwksWithUppercaseKeyType = "{"
                                                  "  \"keys\": ["
                                                  "    {"
                                                  "      \"kty\": \"OCT\","
                                                  "      \"kid\": \"AAECAwQFBgcICQoLDA0ODxAREhM\","
                                                  "      \"k\": \"BAUGBwgJCgsMDQ4PEBESEw\""
                                                  "    }"
                                                  "  ]"
                                                  "}";
    ExtractJWKKeysAndExpect(kJwksWithUppercaseKeyType, false, 0);

    // Wrong key type.
    const std::string kJwksWithWrongKeyType = "{"
                                              "  \"keys\": ["
                                              "    {"
                                              "      \"kty\": \"RSA\","
                                              "      \"kid\": \"AAECAwQFBgcICQoLDA0ODxAREhM\","
                                              "      \"k\": \"BAUGBwgJCgsMDQ4PEBESEw\""
                                              "    }"
                                              "  ]"
                                              "}";
    ExtractJWKKeysAndExpect(kJwksWithWrongKeyType, false, 0);
}

TEST_F(JSONWebKeyTest, Alg)
{
    // 'alg' is ignored, so verify that anything is allowed.
    // Valid alg.
    const std::string kJwksWithValidAlg = "{"
                                          "  \"keys\": ["
                                          "    {"
                                          "      \"kty\": \"oct\","
                                          "      \"alg\": \"A128KW\","
                                          "      \"kid\": \"AAECAwQFBgcICQoLDA0ODxAREhM\","
                                          "      \"k\": \"BAUGBwgJCgsMDQ4PEBESEw\""
                                          "    }"
                                          "  ]"
                                          "}";
    ExtractJWKKeysAndExpect(kJwksWithValidAlg, true, 1);

    // Empty alg.
    const std::string kJwksWithEmptyAlg = "{"
                                          "  \"keys\": ["
                                          "    {"
                                          "      \"kty\": \"oct\","
                                          "      \"alg\": \"\","
                                          "      \"kid\": \"AAECAwQFBgcICQoLDA0ODxAREhM\","
                                          "      \"k\": \"BAUGBwgJCgsMDQ4PEBESEw\""
                                          "    }"
                                          "  ]"
                                          "}";
    ExtractJWKKeysAndExpect(kJwksWithEmptyAlg, true, 1);

    // Alg is case sensitive.
    const std::string kJwksWithLowercaseAlg = "{"
                                              "  \"keys\": ["
                                              "    {"
                                              "      \"kty\": \"oct\","
                                              "      \"alg\": \"a128kw\","
                                              "      \"kid\": \"AAECAwQFBgcICQoLDA0ODxAREhM\","
                                              "      \"k\": \"BAUGBwgJCgsMDQ4PEBESEw\""
                                              "    }"
                                              "  ]"
                                              "}";
    ExtractJWKKeysAndExpect(kJwksWithLowercaseAlg, true, 1);

    // Wrong alg.
    const std::string kJwksWithWrongAlg = "{"
                                          "  \"keys\": ["
                                          "    {"
                                          "      \"kty\": \"oct\","
                                          "      \"alg\": \"RS256\","
                                          "      \"kid\": \"AAECAwQFBgcICQoLDA0ODxAREhM\","
                                          "      \"k\": \"BAUGBwgJCgsMDQ4PEBESEw\""
                                          "    }"
                                          "  ]"
                                          "}";
    ExtractJWKKeysAndExpect(kJwksWithWrongAlg, true, 1);
}

TEST_F(JSONWebKeyTest, CdmSessionType)
{
    ExtractSessionTypeAndExpect(
        "{\"keys\":[{\"k\":\"AQI\",\"kid\":\"AQI\",\"kty\":\"oct\"}]}", true,
        CdmSessionType::TEMPORARY_SESSION);
    ExtractSessionTypeAndExpect(
        "{\"keys\":[{\"k\":\"AQI\",\"kid\":\"AQI\",\"kty\":\"oct\"}],\"type\":"
        "\"temporary\"}",
        true, CdmSessionType::TEMPORARY_SESSION);
    ExtractSessionTypeAndExpect(
        "{\"keys\":[{\"k\":\"AQI\",\"kid\":\"AQI\",\"kty\":\"oct\"}],\"type\":"
        "\"persistent-license\"}",
        true, CdmSessionType::PERSISTENT_LICENSE_SESSION);
    ExtractSessionTypeAndExpect(
        "{\"keys\":[{\"k\":\"AQI\",\"kid\":\"AQI\",\"kty\":\"oct\"}],\"type\":"
        "\"persistent-release-message\"}",
        true, CdmSessionType::PERSISTENT_RELEASE_MESSAGE_SESSION);
    ExtractSessionTypeAndExpect(
        "{\"keys\":[{\"k\":\"AQI\",\"kid\":\"AQI\",\"kty\":\"oct\"}],\"type\":"
        "\"unknown\"}",
        false, CdmSessionType::TEMPORARY_SESSION);
    ExtractSessionTypeAndExpect(
        "{\"keys\":[{\"k\":\"AQI\",\"kid\":\"AQI\",\"kty\":\"oct\"}],\"type\":3}",
        false, CdmSessionType::TEMPORARY_SESSION);
}

TEST_F(JSONWebKeyTest, CreateLicense)
{
    const uint8_t data1[] = { 0x01, 0x02 };
    const uint8_t data2[] = { 0x01, 0x02, 0x03, 0x04 };
    const uint8_t data3[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
        0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 };

    CreateLicenseAndExpect(data1, arraysize(data1),
        CdmSessionType::TEMPORARY_SESSION,
        "{\"kids\":[\"AQI\"],\"type\":\"temporary\"}");
    CreateLicenseAndExpect(
        data1, arraysize(data1), CdmSessionType::PERSISTENT_LICENSE_SESSION,
        "{\"kids\":[\"AQI\"],\"type\":\"persistent-license\"}");
    CreateLicenseAndExpect(
        data1, arraysize(data1),
        CdmSessionType::PERSISTENT_RELEASE_MESSAGE_SESSION,
        "{\"kids\":[\"AQI\"],\"type\":\"persistent-release-message\"}");
    CreateLicenseAndExpect(data2, arraysize(data2),
        CdmSessionType::TEMPORARY_SESSION,
        "{\"kids\":[\"AQIDBA\"],\"type\":\"temporary\"}");
    CreateLicenseAndExpect(data3, arraysize(data3),
        CdmSessionType::PERSISTENT_LICENSE_SESSION,
        "{\"kids\":[\"AQIDBAUGBwgJCgsMDQ4PEA\"],\"type\":"
        "\"persistent-license\"}");
}

TEST_F(JSONWebKeyTest, ExtractLicense)
{
    const uint8_t data1[] = { 0x01, 0x02 };
    const uint8_t data2[] = { 0x01, 0x02, 0x03, 0x04 };
    const uint8_t data3[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
        0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 };

    ExtractKeyFromLicenseAndExpect(
        "{\"kids\":[\"AQI\"],\"type\":\"temporary\"}",
        true,
        data1,
        arraysize(data1));
    ExtractKeyFromLicenseAndExpect(
        "{\"kids\":[\"AQIDBA\"],\"type\":\"temporary\"}",
        true,
        data2,
        arraysize(data2));
    ExtractKeyFromLicenseAndExpect(
        "{\"kids\":[\"AQIDBAUGBwgJCgsMDQ4PEA\"],\"type\":\"persistent\"}",
        true,
        data3,
        arraysize(data3));

    // Try some incorrect JSON.
    ExtractKeyFromLicenseAndExpect("", false, NULL, 0);
    ExtractKeyFromLicenseAndExpect("!@#$%^&*()", false, NULL, 0);

    // Valid JSON, but not a dictionary.
    ExtractKeyFromLicenseAndExpect("6", false, NULL, 0);
    ExtractKeyFromLicenseAndExpect("[\"AQI\"]", false, NULL, 0);

    // Dictionary, but missing expected tag.
    ExtractKeyFromLicenseAndExpect("{\"kid\":[\"AQI\"]}", false, NULL, 0);

    // Correct tag, but empty list.
    ExtractKeyFromLicenseAndExpect("{\"kids\":[]}", false, NULL, 0);

    // Correct tag, but list doesn't contain a string.
    ExtractKeyFromLicenseAndExpect("{\"kids\":[[\"AQI\"]]}", false, NULL, 0);

    // Correct tag, but invalid base64 encoding.
    ExtractKeyFromLicenseAndExpect("{\"kids\":[\"!@#$%^&*()\"]}", false, NULL, 0);
}

TEST_F(JSONWebKeyTest, Base64UrlEncoding)
{
    const uint8_t data1[] = { 0xfb, 0xfd, 0xfb, 0xfd, 0xfb, 0xfd, 0xfb };

    // Verify that |data1| contains invalid base64url characters '+' and '/'
    // and is padded with = when converted to base64.
    std::string encoded_text;
    base::Base64Encode(
        std::string(reinterpret_cast<const char*>(&data1[0]), arraysize(data1)),
        &encoded_text);
    EXPECT_EQ(encoded_text, "+/37/fv9+w==");
    EXPECT_NE(encoded_text.find('+'), std::string::npos);
    EXPECT_NE(encoded_text.find('/'), std::string::npos);
    EXPECT_NE(encoded_text.find('='), std::string::npos);

    // base64url characters '-' and '_' not in base64 encoding.
    EXPECT_EQ(encoded_text.find('-'), std::string::npos);
    EXPECT_EQ(encoded_text.find('_'), std::string::npos);

    CreateLicenseAndExpect(data1, arraysize(data1),
        CdmSessionType::TEMPORARY_SESSION,
        "{\"kids\":[\"-_37_fv9-w\"],\"type\":\"temporary\"}");

    ExtractKeyFromLicenseAndExpect(
        "{\"kids\":[\"-_37_fv9-w\"],\"type\":\"temporary\"}", true, data1,
        arraysize(data1));
}

TEST_F(JSONWebKeyTest, MultipleKeys)
{
    const uint8_t data1[] = { 0x01, 0x02 };
    const uint8_t data2[] = { 0x01, 0x02, 0x03, 0x04 };
    const uint8_t data3[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
        0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 };

    std::vector<uint8_t> result;
    KeyIdList key_ids;
    key_ids.push_back(std::vector<uint8_t>(data1, data1 + arraysize(data1)));
    key_ids.push_back(std::vector<uint8_t>(data2, data2 + arraysize(data2)));
    key_ids.push_back(std::vector<uint8_t>(data3, data3 + arraysize(data3)));
    CreateLicenseRequest(key_ids, CdmSessionType::TEMPORARY_SESSION, &result);
    std::string s(result.begin(), result.end());
    EXPECT_EQ(
        "{\"kids\":[\"AQI\",\"AQIDBA\",\"AQIDBAUGBwgJCgsMDQ4PEA\"],\"type\":"
        "\"temporary\"}",
        s);
}

TEST_F(JSONWebKeyTest, ExtractKeyIds)
{
    const uint8_t data1[] = { 0x01, 0x02 };
    const uint8_t data2[] = { 0x01, 0x02, 0x03, 0x04 };
    const uint8_t data3[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
        0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 };

    KeyIdList key_ids;
    std::string error_message;

    EXPECT_TRUE(ExtractKeyIdsFromKeyIdsInitData("{\"kids\":[\"AQI\"]}", &key_ids,
        &error_message));
    EXPECT_EQ(1u, key_ids.size());
    EXPECT_EQ(0u, error_message.length());
    VerifyKeyId(key_ids[0], data1, arraysize(data1));

    EXPECT_TRUE(ExtractKeyIdsFromKeyIdsInitData(
        "{\"kids\":[\"AQI\",\"AQIDBA\",\"AQIDBAUGBwgJCgsMDQ4PEA\"]}", &key_ids,
        &error_message));
    EXPECT_EQ(3u, key_ids.size());
    EXPECT_EQ(0u, error_message.length());
    VerifyKeyId(key_ids[0], data1, arraysize(data1));
    VerifyKeyId(key_ids[1], data2, arraysize(data2));
    VerifyKeyId(key_ids[2], data3, arraysize(data3));

    // Expect failure when non-ascii.
    EXPECT_FALSE(ExtractKeyIdsFromKeyIdsInitData(
        "This is not ASCII due to \xff\xfe\x0a in it.", &key_ids,
        &error_message));
    EXPECT_EQ(3u, key_ids.size()); // |key_ids| should be unchanged.
    EXPECT_EQ(error_message,
        "Non ASCII: This is not ASCII due to \\u00FF\\u00FE\\n in it.");

    // Expect failure when not JSON or not a dictionary.
    EXPECT_FALSE(ExtractKeyIdsFromKeyIdsInitData("This is invalid.", &key_ids,
        &error_message));
    EXPECT_EQ(3u, key_ids.size()); // |key_ids| should be unchanged.
    EXPECT_EQ(error_message, "Not valid JSON: This is invalid.");
    EXPECT_FALSE(ExtractKeyIdsFromKeyIdsInitData("6", &key_ids, &error_message));
    EXPECT_EQ(3u, key_ids.size()); // |key_ids| should be unchanged.
    EXPECT_EQ(error_message, "Not valid JSON: 6");
    EXPECT_FALSE(ExtractKeyIdsFromKeyIdsInitData(
        "This is a very long string that is longer than 64 characters and is "
        "invalid.",
        &key_ids, &error_message));
    EXPECT_EQ(3u, key_ids.size()); // |key_ids| should be unchanged.
    EXPECT_EQ(error_message,
        "Not valid JSON: This is a very long string that is longer than 64 "
        "characters ...");

    // Expect failure when "kids" not specified.
    EXPECT_FALSE(ExtractKeyIdsFromKeyIdsInitData("{\"keys\":[\"AQI\"]}", &key_ids,
        &error_message));
    EXPECT_EQ(3u, key_ids.size()); // |key_ids| should be unchanged.
    EXPECT_EQ(error_message, "Missing 'kids' parameter or not a list");

    // Expect failure when invalid key_ids specified.
    EXPECT_FALSE(ExtractKeyIdsFromKeyIdsInitData("{\"kids\":[1]}", &key_ids,
        &error_message));
    EXPECT_EQ(3u, key_ids.size()); // |key_ids| should be unchanged.
    EXPECT_EQ(error_message, "'kids'[0] is not string.");
    EXPECT_FALSE(ExtractKeyIdsFromKeyIdsInitData("{\"kids\": {\"id\":\"AQI\" }}",
        &key_ids, &error_message));
    EXPECT_EQ(3u, key_ids.size()); // |key_ids| should be unchanged.
    EXPECT_EQ(error_message, "Missing 'kids' parameter or not a list");

    // Expect failure when non-base64 key_ids specified.
    EXPECT_FALSE(ExtractKeyIdsFromKeyIdsInitData("{\"kids\":[\"AQI+\"]}",
        &key_ids, &error_message));
    EXPECT_EQ(3u, key_ids.size()); // |key_ids| should be unchanged.
    EXPECT_EQ(error_message,
        "'kids'[0] is not valid base64url encoded. Value: AQI+");
    EXPECT_FALSE(ExtractKeyIdsFromKeyIdsInitData("{\"kids\":[\"AQI\",\"AQI/\"]}",
        &key_ids, &error_message));
    EXPECT_EQ(3u, key_ids.size()); // |key_ids| should be unchanged.
    EXPECT_EQ(error_message,
        "'kids'[1] is not valid base64url encoded. Value: AQI/");
}

TEST_F(JSONWebKeyTest, CreateInitData)
{
    const uint8_t data1[] = { 0x01, 0x02 };
    const uint8_t data2[] = { 0x01, 0x02, 0x03, 0x04 };
    const uint8_t data3[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
        0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 };

    KeyIdList key_ids;
    std::string error_message;

    key_ids.push_back(std::vector<uint8_t>(data1, data1 + arraysize(data1)));
    std::vector<uint8_t> init_data1;
    CreateKeyIdsInitData(key_ids, &init_data1);
    std::string result1(init_data1.begin(), init_data1.end());
    EXPECT_EQ(result1, "{\"kids\":[\"AQI\"]}");

    key_ids.push_back(std::vector<uint8_t>(data2, data2 + arraysize(data2)));
    std::vector<uint8_t> init_data2;
    CreateKeyIdsInitData(key_ids, &init_data2);
    std::string result2(init_data2.begin(), init_data2.end());
    EXPECT_EQ(result2, "{\"kids\":[\"AQI\",\"AQIDBA\"]}");

    key_ids.push_back(std::vector<uint8_t>(data3, data3 + arraysize(data3)));
    std::vector<uint8_t> init_data3;
    CreateKeyIdsInitData(key_ids, &init_data3);
    std::string result3(init_data3.begin(), init_data3.end());
    EXPECT_EQ(result3,
        "{\"kids\":[\"AQI\",\"AQIDBA\",\"AQIDBAUGBwgJCgsMDQ4PEA\"]}");
}

} // namespace media
